package com.pokermadness.game;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import ca.ualberta.cs.poker.Hand;
import ca.ualberta.cs.poker.HandEvaluator;

public class Game {
	private boolean isRunning = false;
	// mängijad
	public ArrayList<Player> players;
	// mängu info
	public GameState game;
	// mängu logi
	public ArrayList<LogMessage> log;
	// mängija, kes pole bot
	public static final int HUMAN_PLAYER = 6;
	// palju raha alguses on
	private static final int STARTING_STACK = 100;

	public Collection<String> getWinLog() {
		Collection<String> winLog = new ArrayList<String>();
		for (int i = log.size() - 1; log.get(i).type == LogMessage.WINNER_INFO; i--) {
			winLog.add(log.get(i).content);
		}
		return winLog;
	}

	// kasutaja käik
	public String makeMove(int move) {
		JSONObject info = new JSONObject();
		if (move == 0) {
			players.get(HUMAN_PLAYER - 1).fold();
		} else if (move == 1) {
			players.get(HUMAN_PLAYER - 1).call();
		} else {
			players.get(HUMAN_PLAYER - 1).raise();
		}

		try {
			info.put("move", move);
			info.put("stack", players.get(game.loc - 1).stack);
			info.put("bet", players.get(game.loc - 1).bet);
			info.put("moveLog", log.get(log.size() - 1).content);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		findFirstActive();
		return info.toString();
	}

	// diiler, sb, bb nuppude liigutamine 1 koha võrra edasi
	public void moveButtons() {
		game.loc = game.dealerLocation;
		findFirstActive();
		game.dealerLocation = game.loc;
		findFirstActive();
		game.SBLocation = game.loc;
		findFirstActive();
		game.BBLocation = game.loc;
	}

	// Kood dealer, SB ja BB nupu juhuslikuks paigutamiseks mängu alguses
	public void randomDealer() {

		Random rnd = new Random();

		game.dealerLocation = rnd.nextInt(9) + 1;

		if (game.dealerLocation == 8) {
			game.SBLocation = 9;
			game.BBLocation = 1;
		} else if (game.dealerLocation == 9) {
			game.SBLocation = 1;
			game.BBLocation = 2;
		} else {
			game.SBLocation = game.dealerLocation + 1;
			game.BBLocation = game.dealerLocation + 2;
		}
	}

	// mängijate loomine
	public void initPlayers() {
		players = new ArrayList<Player>();
		for (int i = 1; i < 10; i++) {
			players.add(new Player(i, game, log));
		}
	}

	// kood uue mängu alustamiseks
	public void startGame() {

		game = new GameState();
		initPlayers();

		resetStacks();
		restartActivity();
		resetBets();

		randomDealer();
		setBlinds();

		game.deck.shuffle();

		game.loc = game.BBLocation + 1;

		log.add(new LogMessage("New game has started.", 0,
				LogMessage.START_MESSAGE));
	}

	// kõik, mis toimub mängus
	public JSONObject playRound() throws JSONException {
		JSONObject info = new JSONObject();
		if (checkCondition()) {
			if (game.loc > 9) {
				game.loc = 1;
			}

			int move = players.get(game.loc - 1).makeDecision();
			info.put("player", game.loc);
			info.put("move", move);
			info.put("stack", players.get(game.loc - 1).stack);
			info.put("bet", players.get(game.loc - 1).bet);
			info.put("moveLog", log.get(log.size() - 1).content);

			findFirstActive();

		} else {
			if (game.loc == HUMAN_PLAYER && equalBet()) {
				game.isRoundOver = true;
			} else if (!game.isRoundOver) {
				info.put("player", game.loc);
				info.put("canRaise", players.get(HUMAN_PLAYER - 1).canRaise());
			}
		}

		if (game.isRoundOver) {
			endBettingRound();

			if (game.round < 4) {
				Collection<String> cards = new ArrayList<String>();
				if (game.round == 1) {
					cards.add(game.deck.getCard(18).toString());
					cards.add(game.deck.getCard(19).toString());
					cards.add(game.deck.getCard(20).toString());
				} else if (game.round == 2) {
					cards.add(game.deck.getCard(21).toString());
				} else {
					cards.add(game.deck.getCard(22).toString());
				}
				info.put("newRound", game.round);
				info.put("bankSize", game.bankSize);
				info.put("community", cards);
			} else {
				determineWinner();
				info.put("newRound", game.round);
				info.put("bankSize", game.bankSize);
				info.put("winLog", getWinLog());
				JSONArray contesters = new JSONArray();
				JSONObject contester;
				for (int i = 0; i < 9; i++) {
					if (players.get(i).active) {
						contester = new JSONObject();
						contester.put("no", players.get(i).no);
						contester.put("stack", players.get(i).stack);
						if (countPlayersLeft() > 1) {
							contester.put("card1", game.deck.getCard(i)
									.toString());
							contester.put("card2", game.deck.getCard(i + 9)
									.toString());
						}
						contesters.put(contester);
					}
				}
				info.put("contesters", contesters);
				game.isFinished = true;
			}
		}
		return info;
	}

	// see, mis toimub pärast riverit või kui vaid 1 mängija on järel
	public String finishGame() {
		JSONObject info = new JSONObject();
		game.reset();

		resetTotalBet();
		removeCleaned();
		restartActivity();
		resetBets();

		if (countActive() <= 1) {
			for (int i = 0; i < 9; i++) {
				if (players.get(i).active) {
					try {
						info.put("winner", i + 1);
					} catch (JSONException e) {
						e.printStackTrace();
					}
				}
			}
			return info.toString();
		}

		if (!players.get(HUMAN_PLAYER - 1).active) {
			try {
				info.put("winner", -1);
			} catch (JSONException e) {
				e.printStackTrace();
			}
			return info.toString();
		}

		moveButtons();

		setBlinds();

		game.deck.shuffle();

		findFirstActive();
		return info.toString();
	}

	// võitja kindlaks tegemine
	public int count_sidepot;

	public void determineWinner() {
		if (countPlayersLeft() > 1) {
			List<Integer> bets = new ArrayList<Integer>();
			for (int i = 0; i < 9; i++) {
				bets.add(players.get(i).totalBet);
			}
			List<Integer> discard = new ArrayList<Integer>();
			int minimum = 0;
			count_sidepot = 0;

			do {
				List<Player> winningPlayers = checkHands(discard);

				winningPlayers = sortPlayerHands(winningPlayers);
				List<Integer> pots = calcPots(winningPlayers, bets, minimum);
				if (winningPlayers.get(winningPlayers.size() - 1).totalBet > minimum)
					minimum = winningPlayers.get(winningPlayers.size() - 1).totalBet;
				List<Integer> winSizes = new ArrayList<Integer>();
				int sum = 0;

				for (int i = 0; i < pots.size(); i++) {
					sum = 0;
					for (int j = 0; j <= i; j++) {
						sum += Math.floor(pots.get(j) / (pots.size() - j));
					}
					winSizes.add(sum);
				}

				showWinner(winningPlayers, winSizes, 0);

				for (int i = 0; i < winningPlayers.size(); i++) {
					winningPlayers.get(i).increaseStack(winSizes.get(i));
					discard.add(winningPlayers.get(i).no);
				}
				count_sidepot++;
			} while (!moneyDepleted(bets));

		} else {
			Player winner = null;
			for (int i = 0; i < 9; i++) {
				if (players.get(i).active) {
					winner = players.get(i);
					break;
				}
			}

			if (winner.no != HUMAN_PLAYER) {
				log.add(new LogMessage("Player " + winner.no
						+ " won the main pot (€" + game.bankSize + ").",
						winner.no, LogMessage.WINNER_INFO));
			} else {
				log.add(new LogMessage("You won the main pot (€"
						+ game.bankSize + ").", winner.no,
						LogMessage.WINNER_INFO));
			}

			winner.increaseStack(game.bankSize);
		}
	}

	// võitja väljastamine
	public void showWinner(List<Player> hands, List<Integer> wins, int minimum) {
		int win_no = countWins(wins);
		if (win_no == 0) {
			count_sidepot--;
			return;
		}

		String potName = "";
		String str = "";
		Player tmp = null;
		int winSize = -1;
		int min = 0;
		boolean changed = false;
		int line = -1;
		HandEvaluator eval = new HandEvaluator();
		if (hands.size() > 1) {
			if (count_sidepot == 0) {
				potName = "Main pot ";
			} else {
				potName = "Side pot " + count_sidepot + " ";
			}
			for (int i = 0; i < hands.size(); i++) {
				if (winSize > 0 && wins.get(i) != winSize && !changed) {
					changed = true;
					line = i;
					min = winSize;
				}
				if (wins.get(i) > 0) {
					winSize = wins.get(i);
					tmp = hands.get(i);
					if (hands.get(i).no != HUMAN_PLAYER)
						str += "Player " + hands.get(i).no + ", ";
					else
						str += "You, ";
				}
			}
			if (win_no > 1) {
				str = str.substring(0, str.length() - 2);
				log.add(new LogMessage(potName + "(€" + sumWins(wins, minimum)
						+ ") shared between " + str + ": "
						+ HandEvaluator.nameHand(hands.get(0).hand) + ".", 0,
						LogMessage.WINNER_INFO));
			} else if (win_no == 1) {
				List<Player> list = new ArrayList<Player>();
				list.add(tmp);
				showWinner(list, wins, minimum);
			}

			if (line != -1) {
				count_sidepot++;
				showWinner(hands.subList(line, hands.size()),
						wins.subList(line, wins.size()), min);
			}
			return;
		}

		if (count_sidepot == 0) {
			potName = "main pot ";
		} else {
			potName = "side pot " + count_sidepot + " ";
		}

		Hand hand = eval.getBest5CardHand(hands.get(0).hand);

		str = hand.toString();

		if (hands.get(0).no != HUMAN_PLAYER) {
			log.add(new LogMessage("Player " + hands.get(0).no + " won "
					+ potName + "(€" + sumWins(wins, minimum) + "). "
					+ HandEvaluator.nameHand(hand) + ": " + str + ".", hands
					.get(0).no, LogMessage.WINNER_INFO));
		} else {
			log.add(new LogMessage("You won " + potName + "(€"
					+ sumWins(wins, minimum) + "). "
					+ HandEvaluator.nameHand(hand) + ": " + str + ".", hands
					.get(0).no, LogMessage.WINNER_INFO));
		}
	}

	// kokku liitmine, palju mingis potis raha on
	public int sumWins(List<Integer> wins, int minimum) {
		int sum = 0;
		int start = 0;
		for (int i = 0; i < wins.size(); i++) {
			if (start == 0 && wins.get(i) != 0)
				start = wins.get(i);
			if (wins.get(i) == start)
				sum += wins.get(i) - minimum;
			else
				sum += start - minimum;
		}
		return sum;
	}

	// mitmel võrdse käega mängijal on midagi võita
	public int countWins(List<Integer> wins) {
		int count = 0;
		for (int i = 0; i < wins.size(); i++) {
			if (wins.get(i) > 0)
				count++;
		}
		return count;
	}

	// kontroll, kas raha on otsas
	public boolean moneyDepleted(List<Integer> bets) {
		for (int i = 0; i < 9; i++) {
			if (bets.get(i) != 0)
				return false;
		}
		return true;
	}

	// main pot ja side pot leidmine
	public List<Integer> calcPots(List<Player> winners, List<Integer> bets,
			int minimum) {
		List<Integer> pots = new ArrayList<Integer>();
		int bet;
		int diff;

		for (int i = 0; i < winners.size(); i++) {
			pots.add(i, 0);
			bet = winners.get(i).totalBet;
			if (bet <= minimum)
				continue;
			for (int j = 0; j < 9; j++) {
				if (bet >= players.get(j).totalBet) {
					pots.set(i, pots.get(i) + bets.get(j));
					bets.set(j, 0);
				} else {
					if (i == 0
							|| (i > 0 && winners.get(i - 1).totalBet <= minimum)) {
						pots.set(i, pots.get(i) + bet - minimum);
						bets.set(j, bets.get(j) - bet - minimum);
					} else {
						diff = bet - winners.get(i - 1).totalBet;
						pots.set(i, pots.get(i) + diff);
						bets.set(j, bets.get(j) - diff);
					}
				}
			}
		}

		return pots;
	}

	// paigutab mängijate käed panuse suurenemise järjekorda
	public List<Player> sortPlayerHands(List<Player> playerHands) {
		if (playerHands.size() < 2)
			return playerHands;

		Player temp;
		boolean changed = true;
		while (changed) {
			changed = false;
			for (int i = 1; i < playerHands.size(); i++) {
				if (playerHands.get(i).totalBet < playerHands.get(i - 1).totalBet) {
					temp = playerHands.get(i);
					playerHands.set(i, playerHands.get(i - 1));
					playerHands.set(i - 1, temp);
					changed = true;
				}
			}
		}

		return playerHands;
	}

	// panustamise vooru lõpp
	public void endBettingRound() {
		for (int i = 0; i < 9; i++) {
			game.bankSize += players.get(i).bet;
			players.get(i).bet = 0;
			players.get(i).acted = false;
		}

		if (countPlayersLeft() <= 1) {
			game.round = 4;
			return;
		}

		if (countActive() == 0) {
			// the end
		}

		game.loc = game.dealerLocation;
		findFirstActive();
		game.raiseLimit = 4;
		game.highestBet = 0;
		game.isRoundOver = false;
		game.round++;
		switch (game.round) {
		case 1:
			break;
		case 2:
			game.raiseBy += 2;
			break;
		case 3:
			break;
		default:
			break;
		}
	}

	// esimese mängija leidmine, kes saab käiku teha
	public void findFirstActive() {
		int start = game.loc;
		do {
			game.loc++;
			if (game.loc > 9) {
				game.loc = 1;
			}
			if (game.loc == start) {
				game.isRoundOver = true;
				break;
			}
		} while (!players.get(game.loc - 1).active
				|| players.get(game.loc - 1).stack <= 0);
	}

	// kontroll, kas mängu tsükliga jätkata või ei
	public boolean checkCondition() {
		if ((game.loc != HUMAN_PLAYER || !players.get(HUMAN_PLAYER - 1).active)
				&& !equalBet())
			return true;
		return false;
	}

	// kontrollib, kas kõigil mängu jäänutel on max panus
	public boolean equalBet() {
		if (countActive() == 1) {
			if (canRaise())
				return false;
			return true;
		}
		int comp = game.highestBet;
		if (comp == 0 && !isAllCheck())
			return false;

		for (int i = 0; i < 9; i++) {
			if (players.get(i).bet != comp && players.get(i).active
					&& players.get(i).stack > 0)
				return false;
		}
		game.isRoundOver = true;
		return true;
	}

	// kas ainukese mängu jääja peab oma panust tõstma
	public boolean canRaise() {
		for (int i = 0; i < 9; i++) {
			if (players.get(i).active && players.get(i).stack > 0
					&& players.get(i).bet != game.highestBet)
				return true;
		}
		return false;
	}

	// kas kõik checkisid
	public boolean isAllCheck() {
		for (int i = 0; i < 9; i++) {
			if (players.get(i).active && !players.get(i).acted
					&& players.get(i).stack > 0) {
				return false;
			}
		}
		return true;
	}

	// SB ja BB žetoonide tekitamine lauale...
	public void setBlinds() {
		players.get(game.SBLocation - 1).decreaseStack(1);
		players.get(game.BBLocation - 1).decreaseStack(2);
		game.highestBet = 2;
	}

	// kogu raundi panuse reset
	public void resetTotalBet() {
		for (int i = 0; i < 9; i++) {
			players.get(i).totalBet = 0;
		}
	}

	// mängijate eemaldamine, kellel on raha otsas
	public void removeCleaned() {
		for (int i = 0; i < 9; i++) {
			if (players.get(i).stack <= 0) {
				players.get(i).active = false;
			}
		}
	}

	// aktiivsuse restart
	public void restartActivity() {
		for (int i = 0; i < 9; i++) {
			if (players.get(i).stack > 0)
				players.get(i).active = true;
		}
	}

	// anda igale mängijale uuesti 100 raha
	public void resetStacks() {
		int i;

		for (i = 1; i < 10; i++) {
			players.get(i - 1).stack = STARTING_STACK;
		}
	}

	// panuste nullimine
	public void resetBets() {
		int i;

		for (i = 0; i < 9; i++) {
			players.get(i).bet = 0;
		}
	}

	// mängijate loendamine, kes saavad käike teha
	public int countActive() {
		int res = 0;
		for (int i = 0; i < 9; i++) {
			if (players.get(i).active && players.get(i).stack > 0)
				res++;
		}

		if (res <= 1 && !canRaise()) {
			game.isRoundOver = true;
		}
		return res;
	}

	// mängijate loendamine, kes on veel mängus alles (aga ei pruugi saada käiku
	// teha)
	public int countPlayersLeft() {
		int res = 0;
		for (int i = 0; i < 9; i++) {
			if (players.get(i).active)
				res++;
		}

		return res;
	}

	// mängijate 5-ste käte võrdlus ja parima leidmine
	public List<Player> checkHands(List<Integer> discard) {
		HashMap<Integer, Hand> hands = new HashMap<Integer, Hand>(10);
		Hand hand;
		for (int i = 0; i < 9; i++) {
			if (players.get(i).active && !discard.contains((i + 1))) {
				hand = new Hand();
				hand.addCard(game.deck.getCard(i));
				hand.addCard(game.deck.getCard(i + 9));
				hand.addCard(game.deck.getCard(18));
				hand.addCard(game.deck.getCard(19));
				hand.addCard(game.deck.getCard(20));
				hand.addCard(game.deck.getCard(21));
				hand.addCard(game.deck.getCard(22));
				players.get(i).hand = hand;
				hands.put(i + 1, hand);
			}
		}
		return findWinners(hands);
	}

	public List<Player> findWinners(HashMap<Integer, Hand> hands) {
		List<Player> winners = new ArrayList<Player>();
		HandEvaluator eval = new HandEvaluator();
		Iterator<Entry<Integer, Hand>> it = hands.entrySet().iterator();
		int result;
		Entry<Integer, Hand> best = it.next();
		winners.add(players.get(best.getKey() - 1));
		Entry<Integer, Hand> entry;
		while (it.hasNext()) {
			entry = it.next();
			result = eval.compareHands(best.getValue(), entry.getValue());
			if (result == 2) {
				winners.clear();
				best = entry;
				winners.add(players.get(best.getKey() - 1));
			} else if (result == 0) {
				winners.add(players.get(entry.getKey() - 1));
			}
		}
		return winners;
	}

	public Game() {
		this.log = new ArrayList<LogMessage>();
	}

	public String getInitString(boolean addLog) {
		isRunning = true;
		JSONObject result = new JSONObject();
		JSONArray players = new JSONArray();
		JSONObject player;
		Collection<String> communityCards = new ArrayList<String>();
		try {
			result.put("dealer", game.dealerLocation);
			result.put("SB", game.SBLocation);
			result.put("BB", game.BBLocation);
			result.put("pcard1", game.deck.getCard(5).toString());
			result.put("pcard2", game.deck.getCard(14).toString());
			for (int i = 0; i < 9; i++) {
				player = new JSONObject();
				player.put("no", this.players.get(i).no);
				player.put("active", this.players.get(i).active);
				player.put("stack", this.players.get(i).stack);
				player.put("bet", this.players.get(i).bet);
				player.put("acted", this.players.get(i).acted);
				players.put(player);
			}
			result.put("players", players);
			result.put("round", game.round);
			result.put("bankSize", game.bankSize);
			if (game.round > 0) {
				communityCards.add(game.deck.getCard(18).toString());
				communityCards.add(game.deck.getCard(19).toString());
				communityCards.add(game.deck.getCard(20).toString());
			}
			if (game.round > 1) {
				communityCards.add(game.deck.getCard(21).toString());
			}
			if (game.round > 2) {
				communityCards.add(game.deck.getCard(22).toString());
			}
			if (game.isFinished) {
				JSONArray contesters = new JSONArray();
				JSONObject contester;
				for (int i = 0; i < 9; i++) {
					if (this.players.get(i).active) {
						contester = new JSONObject();
						contester.put("no", this.players.get(i).no);
						if (countPlayersLeft() > 1) {
							contester.put("card1", game.deck.getCard(i)
									.toString());
							contester.put("card2", game.deck.getCard(i + 9)
									.toString());
						}
						contesters.put(contester);
					}
				}
				result.put("contesters", contesters);
			}
			result.put("community", communityCards);
			if (addLog) {
				JSONArray logMessages = new JSONArray();
				JSONObject logMessage;
				for (int i = 0; i < log.size(); i++) {
					logMessage = new JSONObject();
					logMessage.put("player", log.get(i).player);
					logMessage.put("type", log.get(i).type);
					logMessage.put("content", log.get(i).content);
					logMessages.put(logMessage);
				}
				result.put("log", logMessages);
			}
			return result.toString();
		} catch (JSONException e) {
			e.printStackTrace();
			return "error";
		}
	}

	public String getIsRunningString() {
		JSONObject result = new JSONObject();
		try {
			result.put("isRunning", isRunning);
			return result.toString();
		} catch (JSONException e) {
			e.printStackTrace();
			return "error";
		}
	}
}
